﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;
using NSsh.Server.Services;
using System.Security.Principal;
using NSsh.Server.Utility;
using log4net;
using NSsh.Server.Types;

namespace NSsh.Server.ChannelLayer.Console
{
    public abstract class BaseProcessConsole : IConsole
    {
        /// <summary>
        /// Logging support for this class.
        /// </summary>
        private static readonly ILog log = LogManager.GetLogger(typeof(BaseProcessConsole));

        private StreamReader _standardInReader;
        private FifoStream _standardInBuffer;
        private StreamWriter _standardInWriter;

        public BaseProcessConsole(IImpersonationProvider impersonationProvider, IIdentity authenticatedIdentity, string password,
            string executable, string arguments, string workingDirectory)
        {
            ProcessStartInfo psi = new ProcessStartInfo()
            {
                WindowStyle = ProcessWindowStyle.Hidden,
                CreateNoWindow = true,
                RedirectStandardError = true,
                RedirectStandardOutput = true,
                RedirectStandardInput = true,
                UseShellExecute = false,
                ErrorDialog = false,
                WorkingDirectory = workingDirectory,
                FileName = executable,
                Arguments = arguments                
            };

            log.Debug(string.Format("Process start info: '{0}' '{1}' '{2}'", psi.FileName, psi.Arguments, psi.WorkingDirectory));

            // TODO: Ideally here we don't require the user's password in case they authenticate via some other means
            // instead it would be good if you could impersonate their identity and create a process under their user
            // or use CreateProcessAsUser or similar...
            this.ProcessDetails = impersonationProvider.CreateProcess(authenticatedIdentity, password, psi);

            this.ProcessDetails.Process.Exited += delegate
            {
                if (Closed != null) Closed(this, EventArgs.Empty);
            };

            _standardInBuffer = new FifoStream();
            _standardInReader = new StreamReader(_standardInBuffer, Encoding.ASCII);
            _standardInWriter = new StreamWriter(_standardInBuffer, Encoding.ASCII);
                        
            _standardInBuffer.DataArrived += delegate
            {
                lock (this)
                {
                    string data = _standardInReader.ReadToEnd();

                    log.Debug("StdIn:" + data);

                    OnStandardInputData(data);
                }
            };
        }

        public virtual void OnStandardInputData(string data)
        {
            data = data.Replace("\r", "\r\n");
            ProcessDetails.StandardInput.Write(data);
            ProcessDetails.StandardInput.Flush();
        }

        public ProcessDetails ProcessDetails { get; private set; }

        #region IConsole Members

        public virtual TextWriter StandardInput
        {
            get { return _standardInWriter; }
        }

        public virtual TextReader StandardOutput
        {
            get { return ProcessDetails.StandardOutput; }
        }

        public virtual TextReader StandardError
        {
            get { return ProcessDetails.StandardError; }
        }

        public void Close()
        {
            if (!ProcessDetails.Process.HasExited)
            {
                log.Debug("Killing child process");
                ProcessDetails.Process.Kill();
            }
            else
            {
                log.Debug("Process exited: " + ProcessDetails.Process.ExitCode);
            }
        }

        public bool HasClosed
        {
            get
            {
                return ProcessDetails.Process.HasExited;
            }
        }

        public event EventHandler Closed;

        #endregion
    }
}
